渲染进程加载本地资源的方案?file:// 有什么问题?
考察点
- file:// 协议的限制认知
- 自定义协议加载资源的完整实践
参考答案
1. file:// 加载的问题
win.loadFile('index.html') 本质是 file:// 协议加载,开发简单但限制多:
| 问题 | 说明 |
|---|---|
| fetch/XHR 受限 | file:// 下 fetch 本地文件被 CORS 拦截(file origin 为 null) |
| ES Module 加载失败 | <script type="module"> 的 import 在 file:// 下跨文件引用受限 |
| origin 是 null | localStorage/IndexedDB 按 null origin 处理,行为怪异且多窗口可能共享 |
| 安全上下文 | 非 secure context,crypto.subtle 等 API 不可用 |
| Service Worker | 无法注册 |
| 路径分隔符/编码 | Windows 反斜杠、中文路径编码的边角问题 |
2. 现代方案:自定义协议(protocol.handle)
// ready 前注册特权 scheme
protocol.registerSchemesAsPrivileged([
{
scheme: 'myapp',
privileges: {
standard: true, // 标准 URL 解析
secure: true, // 安全上下文(crypto.subtle 可用)
supportFetchAPI: true, // fetch 可用
corsEnabled: true,
stream: true,
},
},
]);
app.whenReady().then(() => {
// 注册处理器:把 myapp:// 映射到本地文件
protocol.handle('myapp', (request) => {
const { pathname } = new URL(request.url);
const filePath = path.resolve(RENDERER_ROOT, '.' + decodeURIComponent(pathname));
// 防路径穿越(见安全篇)
if (!filePath.startsWith(RENDERER_ROOT + path.sep)) {
return new Response('forbidden', { status: 403 });
}
return net.fetch(pathToFileURL(filePath).toString());
});
win.loadURL('myapp://app/index.html'); // 页面有了正常的 origin
});
效果:myapp://app 成为标准 origin,fetch、module、storage、安全上下文全部正常。
3. 用户文件/动态资源的加载
应用资源(打包内的 html/js/css)走 myapp:// 协议;用户文件(图片、视频、文档,路径动态且不在安装目录)需要另一个通道:
(1)专用媒体协议
// media:// 协议专门服务用户文件
protocol.handle('media', (request) => {
const { pathname } = new URL(request.url);
const filePath = decodeURIComponent(pathname);
// 白名单根目录(只允许用户数据目录下的文件)
const allowed = path.resolve(filePath).startsWith(USER_DATA_ROOT);
if (!allowed) return new Response('forbidden', { status: 403 });
return net.fetch(pathToFileURL(filePath).toString());
});
<!-- 渲染进程使用 -->
<img src="media:///Users/xxx/Library/myapp/cache/photo.png" />
<video src="media:///path/to/video.mp4" /> <!-- stream: true 支持拖动播放 -->
(2)转 data URL / blob(小文件场景)
// 小图标等小文件,主进程读文件转 base64 经 IPC 返回
const dataUrl = await window.api.readAsDataUrl(filePath);
img.src = dataUrl;
缺点:大文件内存翻倍(文件内容 + base64 字符串 + 解码后数据),只用于小文件。
4. 方案选型表
| 资源类型 | 方案 |
|---|---|
| 应用页面/静态资源 | 自定义协议(myapp://) |
| 用户媒体文件(图/视频) | 专用媒体协议 + 根目录白名单 |
| 小文件(几 KB 图标) | IPC 转 data URL |
| 超大文件(GB 级视频流) | 本地 HTTP 服务(Range 支持好) |
5. 注意点
- registerSchemesAsPrivileged 必须在 ready 前,忘了调会发现 fetch/storage 全异常
- 协议 handler 里必须防路径穿越(resolve 后校验根目录前缀)
stream: true让协议响应支持流式(视频拖动播放、大文件边下边播)- 开发环境用 vite dev server(http://localhost),生产用自定义协议——两种环境 origin 不同,代码里不要硬编码 origin
- CSP 配置要把自定义 scheme 加进白名单(
img-src myapp: media:)
面试话术
file:// 的问题可以归结为"origin 是 null"引发的一系列异常:fetch 被 CORS 拦、ES Module 受限、storage 行为怪异、非安全上下文。标准解法是注册特权自定义协议加载应用资源(ready 前注册特权、handle 里防路径穿越);用户文件走独立媒体协议 + 根目录白名单,小文件才用 data URL 偷懒,GB 级流媒体走本地 HTTP 服务。
常见追问
- dev 用 http dev server、prod 用自定义协议,代码怎么兼容? 构建时注入 BASE_URL 环境变量,资源引用全部走变量;或以 dev server 为准,prod 协议 handler 内部转发到同样的路径结构。
- 协议响应慢怎么优化? handler 里做缓存(静态资源哈希指纹 + 强缓存头);避免每次请求都 stat 文件。